// License Notification
//======================================================
// Copyright (c) 2010 Channel Intelligence Inc.
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// which accompanies this distribution, and is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// ======================================================




using System;
using System.IO;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
using System.Text;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using CIP4.Common;
using CIP4.Item;
using CIP4.DataProvider;
using CIP4.Simple;

namespace CIP4.DataProvider.Dictionary
{
    public class DataProvider : CIP4.Simple.DataProvider, IDataProvider
    {
        private ItemDictionariesType ItemDictionaries;

        DirectoryInfo ProviderDirectory;

        IDataProvider Cache { get; set; } // kind of silly for a dictionary DataProvider

        Dictionary<Guid, string> Collections = new Dictionary<Guid, string>();

        bool Temporary { get; set; }

        #region IDataProvider Members

        public string ConnectionString { get; private set; }

        public DataProvider (CIP4ConnectionAttribute DataProviderConnection, IDataProvider cachingProvider, LoggerDelegate logger)
            : base (DataProviderConnection)
        {
            Cache = cachingProvider;
            if (!(DataProviderConnection is CIP4DictionaryConnectionAttribute))
                LoggerWrapper(MajorEnum.Error, MinorEnum.UnexpectedConnectionType,
                    "{0} is not an acceptable connection type", new object[] { DataProviderConnection.ToString() }, logger);
            else
            {
                LoggerWrapper(MajorEnum.Trace, MinorEnum.InitializingProvider,
                    "Initalizing Provider: {0}", new object[] { DataProviderConnection }, logger);
                CIP4DictionaryConnectionAttribute connection = (CIP4DictionaryConnectionAttribute)DataProviderConnection;
                ConnectionString = connection.ToString();
                ProviderDirectory = new DirectoryInfo(connection.BaseDirectoryPath);
                Temporary = connection.Temporary;
                if (!ProviderDirectory.Exists)
                    throw new ArgumentException(string.Format("The directory {0} does not exist - the file DataProvider provider requires an existing directory", ProviderDirectory.FullName));
                ItemDictionaries = new ItemDictionariesType(ProviderDirectory.FullName);
            }
        }

        public void DeleteCollection(CollectionType collection, LoggerDelegate logger)
        {
            using (new CollectionLockType(collection.Name))
            {
                if (!LoggerWrapper(MajorEnum.Trace, MinorEnum.ActionStart,
                    "Started {0}", new object[] { MinorEnum.DeleteCollection }, logger))
                    return;

                if (!ItemDictionaries.ContainsCollection(collection.Name))
                    return;
                ItemDictionaries.RemoveCollection(collection.Name);
                try
                {
                    if (!Temporary)
                        File.Delete(ItemDictionaries.CollectionsDirectory + "\\" + collection.ExternalIDString() + ".data");
                    ItemDictionaries.RemoveCollection(collection.Name);
                }
                catch (FileNotFoundException)
                {
                }
                LoggerWrapper(MajorEnum.Information, MinorEnum.DeleteCollection,
                    "Collection deleted {0}", new object[] { collection }, logger);
            }
        }

        public override IEnumerable<object> GetItems(CollectionType collection, IBooleanValue selectionExpression, CursorType cursor)
        {
            using (new CollectionLockType(collection.Name))
            {
                List<object> items = new List<object>();
                if (!LoggerWrapper(MajorEnum.Trace, MinorEnum.ActionStart,
                    "Started {0}", new object[] { MinorEnum.GetItems }, cursor.Logger))
                    return items;

                try
                {
                    Dictionary<string, string> collectionDictionary = ItemDictionaries.GetCollection(collection.Name, Temporary);

                    lock (collectionDictionary)
                    {
                        ISerializerType serializer = cursor.GetSerializer(SerializerKindEnum.CIP4Xml);

                        string lookForExternalId = GetExternalIdValueFromSelectionExpression(selectionExpression, serializer);

                        if (!string.IsNullOrEmpty(lookForExternalId))
                        {
                            string objectText = collectionDictionary.ContainsKey(lookForExternalId)? collectionDictionary[lookForExternalId] : null;
                            if (objectText != null)
                            {
                                object item = serializer.GetObjectForData(XElement.Parse(objectText));
                                if (collectionDictionary.ContainsKey(lookForExternalId) &&
                                    CompareProperties(selectionExpression, item, serializer))
                                    items.Add(item);
                            }
                        }
                        else
                        {
                            if (cursor.FailOnLinearSearch)
                                if (!LoggerWrapper(MajorEnum.Error, MinorEnum.FailOnLinearSearch,
                                    "Failed on linear search accessing {0}", new object[] { collection }, cursor.Logger))
                                    return items;

                            bool foundMarker = string.IsNullOrEmpty(cursor.Marker);
                            foreach (string itemString in collectionDictionary.Values)
                            {
                                object item = serializer.GetObjectForData(XElement.Parse(itemString));
                                if (foundMarker && CompareProperties(selectionExpression, item, serializer))
                                {
                                    items.Add(item);
                                    cursor.Marker = serializer.GetExternalID(item).ToString();
                                }
                                foundMarker = foundMarker || serializer.GetExternalID(item).ToString() == cursor.Marker;
                                if (items.Count == cursor.PageSize)
                                    break;
                            }
                        }
                    }
                    return items;
                }
                finally
                {
                    LoggerWrapper(MajorEnum.Information, MinorEnum.GetItems,
                        "Get {0} items from {1}", new object[] { items.Count, collection }, cursor.Logger);
                }
            }
        }

        public override void PurgeItems(IEnumerable<ExternalIDType> externalIDs, CollectionType collection, GetSerializerDelegate getSerializer)
        {
            using (new CollectionLockType(collection.Name))
            {
                ISerializerType serializer = getSerializer(SerializerKind);

                if (!LoggerWrapper(MajorEnum.Trace, MinorEnum.ActionStart,
                    "Started {0}", new object[] { MinorEnum.PurgeItems }, serializer.Logger))
                    return;

                Dictionary<string, string> collectionDictionary = ItemDictionaries.GetCollection(collection.Name, Temporary);

                if (collectionDictionary == null)
                    return;

                int deletedCount = 0;
                lock (collectionDictionary)
                {
                    foreach (ExternalIDType externalID in externalIDs)
                    {
                        if (collectionDictionary.Remove(externalID.ToString()))
                        {
                            deletedCount++;
                            LoggerWrapper(MajorEnum.Trace, MinorEnum.ItemDeleted, "deleted item {0}", new object[] { externalID }, serializer.Logger);
                        }
                    }
                }
                if (deletedCount > 0)
                {
                    if (!Temporary)
                        ItemDictionaries.SaveCollection(collection.Name);
                }
                else
                {
                    LoggerWrapper(MajorEnum.Warning, MinorEnum.NothingDeleted, "Nothing deleted from {0}", new object[] { collection.Name }, serializer.Logger);
                }
                LoggerWrapper(MajorEnum.Information, MinorEnum.PurgeItems,
                    "{0} items deleted from {1}", new object[] { deletedCount, collection.Name }, serializer.Logger);
            }
        }

        public void SaveItems(IEnumerable<object> items, CollectionType collection, GetSerializerDelegate getSerializer)
        {
            using (new CollectionLockType(collection.Name))
            {
                ISerializerType serializer = getSerializer(SerializerKind);

                if (!LoggerWrapper(MajorEnum.Trace, MinorEnum.ActionStart,
                    "Started {0}", new object[] { MinorEnum.SaveItems }, serializer.Logger))
                    return;

                Dictionary<string, string> collectionDictionary = ItemDictionaries.GetCollection(collection.Name, Temporary);
                int savedCount = 0;
                lock (collectionDictionary)
                {
                    foreach (object itemObject in items)
                    {
                        string xmlText = (string)serializer.GetDataForObject(itemObject);
                        string externalID = serializer.GetExternalID(itemObject).ToString();
                        if (collectionDictionary.ContainsKey(externalID))
                        {
                            string existingItem = collectionDictionary[externalID];
                            if (xmlText == existingItem)
                                continue;
                            collectionDictionary[externalID] = xmlText;
                            savedCount++;
                            LoggerWrapper(MajorEnum.Trace, MinorEnum.ItemsUpdated, "Items {0} updated", new object[] { externalID }, serializer.Logger);
                        }
                        else
                        {
                            if (!SetHeaderProperties(itemObject, collection, serializer))
                                continue;

                            collectionDictionary.Add(externalID, xmlText);
                            savedCount++;
                            LoggerWrapper(MajorEnum.Trace, MinorEnum.ItemsUpdated, "Items {0} updated", new object[] { externalID }, serializer.Logger);
                        }
                    }
                }
                if (savedCount > 0)
                {
                    if (!Temporary)
                        ItemDictionaries.SaveCollection(collection.Name);
                }
                LoggerWrapper(MajorEnum.Information, MinorEnum.SaveItems,
                    "{0} items saved in {1}", new object[] { savedCount, collection.Name }, serializer.Logger);
            }
        }

        public void Terminate(LoggerDelegate logger)
        {
        }

        #endregion

    }
}
